﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using com.calitha.goldparser;
using System.IO;
using System.Runtime.Serialization;


namespace GIMachineGenerator
{
    public class GIGenParser
    {
        private string _cgtFilePath = "";
        private string _folderPath = "";
        private LALRParser parser = null;
        private Dictionary<string, GIMachine> _privateMachineTable = new Dictionary<string, GIMachine>();
        private Dictionary<string, GIMachine> _publicMachineTable = new Dictionary<string, GIMachine>();

        public GIGenParser()
        {
            string sThisDLLPath = Assembly.GetAssembly(typeof(GIGenParser)).Location;
            _folderPath = sThisDLLPath.Substring(0, sThisDLLPath.LastIndexOf(@"\")+1);
            this._cgtFilePath = _folderPath + "GrammerIntelliForJs.cgt";
            Init();
        }

        private void Init()
        {
            FileStream stream = new FileStream(_cgtFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            CGTReader reader = new CGTReader(stream);
            parser = reader.CreateNewParser();
            parser.TrimReductions = false;
            parser.StoreTokens = LALRParser.StoreTokensMode.NoUserObject;

            parser.OnTokenError += new LALRParser.TokenErrorHandler(parser_OnTokenError);
            parser.OnParseError += new LALRParser.ParseErrorHandler(parser_OnParseError);
            parser.OnAccept += new LALRParser.AcceptHandler(parser_OnAccept);
            stream.Close();
        }

        void parser_OnAccept(LALRParser parser, AcceptEventArgs args)
        {
            int dummy = 100;
        }

        void parser_OnParseError(LALRParser parser, ParseErrorEventArgs args)
        {
            throw new Exception("Parse error caused by token: '" + args.UnexpectedToken.ToString() + "'");
        }

        void parser_OnTokenError(LALRParser parser, TokenErrorEventArgs args)
        {
            throw new Exception("Token error with input: '" + args.Token.ToString() + "'");
        }


        /// <summary>
        /// Parses a source code based on the GIGen language. Returns a GIMachine
        /// </summary>
        /// <param name="patternSouceCode"></param>
        /// <returns></returns>
        public GIMachine ParseGIGenSource(string patternSouceCode, string machineName)
        {
            GIMachine machine = null;

            NonterminalToken token = parser.Parse(patternSouceCode);
            if (token != null)
            {
                machine = (GIMachine)CreateObject(token);
            }

            // Enumerate the public machine table & construct the final table
            // Final machine is an OR or all public machines
            // NOTE: since we'll dump the tables after the final machine construction, there is no need to 'clone'
            GIMachine m = new GIMachine();
            foreach (KeyValuePair<string, GIMachine> KV in _publicMachineTable)
            {
                GIMachine pm = KV.Value;
                if (pm != null && pm.States != null && pm.States.Count > 0)
                {
                    m.States.AddRange(pm.States);
                }
            }

            m.Name = machineName;

            // end of it clear everything
            _publicMachineTable.Clear();
            _privateMachineTable.Clear();

            return m;
        }

        private Object CreateObject(Token token)
        {
            if (token is TerminalToken)
                return CreateObjectFromTerminal((TerminalToken)token);
            else
                return CreateObjectFromNonterminal((NonterminalToken)token);
        }

        private Object CreateObjectFromTerminal(TerminalToken token)
        {
            switch (token.Symbol.Id)
            {
                case (int)SymbolConstants.SYMBOL_EOF:
                    return null;
                case (int)SymbolConstants.SYMBOL_WHITESPACE:
                    return null;
                case (int)SymbolConstants.SYMBOL_CAPTURENAME:
                    return null;
                case (int)SymbolConstants.SYMBOL_CLOSEBRAC:
                    return null;
                case (int)SymbolConstants.SYMBOL_DOLLER:
                    return null;
                case (int)SymbolConstants.SYMBOL_EQUALTO:
                    return null;
                case (int)SymbolConstants.SYMBOL_ERROR:
                    return null;
                case (int)SymbolConstants.SYMBOL_GREATERTHAN:
                    return null;
                case (int)SymbolConstants.SYMBOL_IDENTIFIER:
                    return token.Text;
                case (int)SymbolConstants.SYMBOL_LESSTHAN:
                    return null;
                case (int)SymbolConstants.SYMBOL_OPENBRAC:
                    return null;
                case (int)SymbolConstants.SYMBOL_OPTIONALOPERATOR:
                    return null;
                case (int)SymbolConstants.SYMBOL_OROPERATOR:
                    return null;
                case (int)SymbolConstants.SYMBOL_PATTERN:
                    return null;
                case (int)SymbolConstants.SYMBOL_PATTERNNAME:
                    return null;
                case (int)SymbolConstants.SYMBOL_PATTERNREFERENCE:
                    return null;
                case (int)SymbolConstants.SYMBOL_PRIVATESTATEMENT:
                    return null;
                case (int)SymbolConstants.SYMBOL_PROGRAM:
                    {
                        return null;
                    }
                case (int)SymbolConstants.SYMBOL_PUBLICOPERATOR:
                    return null;
                case (int)SymbolConstants.SYMBOL_PUBLICSTATEMENT:
                    return null;
                case (int)SymbolConstants.SYMBOL_REGEXSTRING:
                    return null;
                case (int)SymbolConstants.SYMBOL_SEMICOLON:
                    return null;
                case (int)SymbolConstants.SYMBOL_SIMPLEASSIGNMENT:
                    return null;
                case (int)SymbolConstants.SYMBOL_SIMPLEASSIGNMENTPATTERN:
                    return null;
                case (int)SymbolConstants.SYMBOL_STATEMENT:
                    return null;
                case (int)SymbolConstants.SYMBOL_STATEMENTS:
                    return null;
                case (int)SymbolConstants.SYMBOL_STRINGLITERAL:
                    return null;
                case (int)SymbolConstants.SYMBOL_WORD:
                    return null;

            }
            throw new SymbolException("Unknown symbol");
        }

        public Object CreateObjectFromNonterminal(NonterminalToken token)
        {
            switch (token.Rule.Id)
            {
                case (int)RuleConstants.RULE_CAPTURENAME_IDENTIFIER:
                    {/// <c> <CaptureName> ::= Identifier </c>
                        return token.Tokens[0].ToString();
                    }

                case (int)RuleConstants.RULE_PATTERN:
                    {/// <c> <Pattern> ::= <Word> </c>
                        string sWord = (string) CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                        GIMachineWordState oLiteralWord = new GIMachineWordState();
                        oLiteralWord.Pattern = sWord; oLiteralWord.Name = ""; oLiteralWord.IsLiteral = true; oLiteralWord.IsAcceptable = true;
                        GIMachine omachine = new GIMachine();
                        omachine.States.Add(oLiteralWord);
                        return omachine;
                    }

                case (int)RuleConstants.RULE_PATTERN_OPENBRAC_CLOSEBRAC:
                    {/// <c> <Pattern> ::= OpenBrac <Pattern> CloseBrac </c>
                        return CreateObjectFromNonterminal(token.Tokens[1] as NonterminalToken) ;
                    }

                case (int)RuleConstants.RULE_PATTERN_OPTIONALOPERATOR:
                    {/// <c> <Pattern> ::= <Pattern> OptionalOperator </c>
                        GIMachine omachine = (GIMachine)CreateObjectFromNonterminal( token.Tokens[0] as NonterminalToken);
                        return HandleOptionalOperator(omachine);
                    }

                case (int)RuleConstants.RULE_PATTERN_OROPERATOR:
                    {/// <c> <Pattern> ::= <Pattern> OrOperator <Pattern> </c>
                        GIMachine m1 = (GIMachine)CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                        GIMachine m2 = (GIMachine)CreateObjectFromNonterminal(token.Tokens[2] as NonterminalToken);

                        return HandleOrOperator(m1, m2);
                    }

                case (int)RuleConstants.RULE_PATTERN2:
                    {/// <c> <Pattern> ::= <RegexString> </c>
                        GIMachine omachine = (GIMachine) CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                        
                        return omachine;
                    }

                case (int)RuleConstants.RULE_PATTERN3:
                    {/// <c> <Pattern> ::= <PatternReference> </c>

                        return CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken) ;
                    }

                case (int)RuleConstants.RULE_PATTERN4:
                    {/// <c> <Pattern> ::= <Pattern> <Pattern> </c>
                        GIMachine m1 = (GIMachine)CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                        GIMachine m2 = (GIMachine)CreateObjectFromNonterminal(token.Tokens[1] as NonterminalToken);

                        return ConcatOperator(m1, m2);
                    }

                case (int)RuleConstants.RULE_PATTERNNAME_IDENTIFIER:
                    {/// <c> <PatternName> ::= Identifier </c>
                        return token.Tokens[0].ToString();
                    }

                case (int)RuleConstants.RULE_PATTERNREFERENCE_DOLLER_IDENTIFIER_LESSTHAN_GREATERTHAN:
                    {/// <c> <PatternReference> ::= Doller Identifier LessThan <CaptureName> GreaterThan </c>
                        string sMachineRefName = (string)CreateObject(token.Tokens[1]), sCaptureName = (string) CreateObject( token.Tokens[3] );
                        GIMachine omachine = GetMachineFromParsedTable(sMachineRefName).Clone(sCaptureName);

                        MarkFinalStatesAs(ref omachine, sCaptureName);

                        return omachine;
                    }

                case (int)RuleConstants.RULE_PRIVATESTATEMENT:
                    {/// <c> <PrivateStatement> ::= <SimpleAssignment> </c>
                        token.Tokens[0].UserObject = new ParseFlowContext(true, null);
                        return CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                    }
                case (int)RuleConstants.RULE_PRIVATESTATEMENT_EQUALTO:
                    {/// <c> <PrivateStatement> ::= <PatternName> EqualTo <Pattern> </c>
                        string sName = (string)CreateObject(token.Tokens[0]);
                        GIMachine omachine = (GIMachine) CreateObjectFromNonterminal(token.Tokens[2] as NonterminalToken);
                        omachine.Name = sName;
                        return omachine;
                    }

                case (int)RuleConstants.RULE_PROGRAM:
                    {   /// <c> <Program> ::= <Statements> </c>

                        if (token.Tokens != null && token.Tokens.Length == 1)
                        {
                            Token t = token.Tokens[0];
                            if (t is TerminalToken)
                            {
                                return CreateObjectFromTerminal(t as TerminalToken);
                            }
                            else
                            {
                                return CreateObjectFromNonterminal(t as NonterminalToken);
                            }
                        }

                        return null;
                    }

                case (int)RuleConstants.RULE_PUBLICSTATEMENT_PUBLICOPERATOR:
                    {/// <c> <PublicStatement> ::= PublicOperator <SimpleAssignment> </c>
                        ParseFlowContext oContext = new ParseFlowContext(false, null);
                        token.Tokens[1].UserObject = oContext;
                        return CreateObjectFromNonterminal( token.Tokens[1] as NonterminalToken );
                    }

                case (int)RuleConstants.RULE_PUBLICSTATEMENT_PUBLICOPERATOR_EQUALTO:
                    {/// <c> <PublicStatement> ::= PublicOperator <PatternName> EqualTo <Pattern> </c>
                        string sPublicMachineName = (string) CreateObjectFromNonterminal(token.Tokens[1] as NonterminalToken);
                        GIMachine omachine = (GIMachine)CreateObjectFromNonterminal(token.Tokens[3] as NonterminalToken);
                        omachine.Name = sPublicMachineName;
                        AddPublicMachine(omachine);
    
                        return omachine;
                    }

                case (int)RuleConstants.RULE_REGEXSTRING_STRINGLITERAL_LESSTHAN_GREATERTHAN:
                    {/// <c> <RegexString> ::= StringLiteral LessThan <CaptureName> GreaterThan </c>
                        string sPattern = (string)CreateObject(token.Tokens[0]), sName = (string) CreateObject( token.Tokens[2] );
                        GIMachine omachine = new GIMachine();
                        omachine.Name = sName;
                        GIMachineWordState oState = new GIMachineWordState();
                        oState.Name = sName; oState.IsLiteral = false; oState.Pattern = sPattern; oState.IsAcceptable = true;
                        omachine.States.Add(oState);

                        return omachine;
                    }

                case (int)RuleConstants.RULE_SIMPLEASSIGNMENT_EQUALTO:
                    {/// <c> <SimpleAssignment> ::= <PatternName> EqualTo <SimpleAssignmentPattern> </c>
                        string sPatternName = (string) CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken),
                               sRegex       = (string) CreateObjectFromNonterminal(token.Tokens[2] as NonterminalToken);
                        
                        GIMachine omachine = new GIMachine(); 
                        omachine.Name = sPatternName;
                        GIMachineWordState oState = new GIMachineWordState();
                        oState.Pattern = sRegex; oState.Name = sPatternName; oState.IsLiteral = false; oState.IsAcceptable = true;
                        omachine.States.Add( oState );

                        if (token.UserObject != null && !((ParseFlowContext)token.UserObject).IsPrivate)
                        {   // Public machine
                            AddPublicMachine(omachine);
                        }
                        else
                        {   // Private machine
                            AddPrivateMachine(omachine);
                        }

                        return omachine;
                    }

                case (int)RuleConstants.RULE_SIMPLEASSIGNMENTPATTERN_STRINGLITERAL:
                    {/// <c> <SimpleAssignmentPattern> ::= StringLiteral </c>
                        string s = token.Tokens[0].ToString();
                        return s.Substring(1, s.Length - 2);
                    }

                case (int)RuleConstants.RULE_STATEMENT:
                    {/// <c> <Statement> ::= <PrivateStatement> </c>
                        ParseFlowContext oContext = new ParseFlowContext(true, null);
                        token.Tokens[0].UserObject = oContext;
                        return CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                    }

                case (int)RuleConstants.RULE_STATEMENT2:
                    {/// <c> <Statement> ::= <PublicStatement> </c>
                        ParseFlowContext oContext = new ParseFlowContext(false, null);
                        token.Tokens[0].UserObject = oContext;
                        return CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                    }

                case (int)RuleConstants.RULE_STATEMENTS:
                    {/// <c> <Statements> ::=  </c>
                        return null;
                    }

                case (int)RuleConstants.RULE_STATEMENTS_SEMICOLON:
                    {/// <c> <Statements> ::= <Statement> SemiColon <Statements> </c>
                        CreateObjectFromNonterminal(token.Tokens[0] as NonterminalToken);
                        // ignore semicolon
                        CreateObjectFromNonterminal(token.Tokens[2] as NonterminalToken);
                        return null;
                    }

                case (int)RuleConstants.RULE_WORD_IDENTIFIER:
                    {/// <c> <Word> ::= Identifier </c>
                        return token.Tokens[0].ToString();
                    }

            }
            throw new RuleException("Unknown rule");
        }

        private GIMachine HandleOptionalOperator(GIMachine omachine)
        {
            // TODO: as of now we just return the incoming machine

            return omachine;
        }

        // m ->  m1 | m2
        private GIMachine HandleOrOperator(GIMachine m1, GIMachine m2)
        {
            GIMachine m = new GIMachine();

            foreach (GIMachineWordState S1 in m1.States)
            {
                m.States.Add(S1.Clone());
            }

            foreach (GIMachineWordState S2 in m2.States)
            {
                m.States.Add(S2.Clone());
            }

            return m;
        }

        // m -> m1 m2
        private GIMachine ConcatOperator(GIMachine m1, GIMachine m2)
        {

            GIMachine m = new GIMachine();

            foreach (GIMachineWordState S1 in m1.States)
            {
                m.States.Add(S1.Clone());
            }
            
            List<GIMachineWordState> finalStatesOfM1 = GetFinalStates(m);

            foreach (GIMachineWordState S in finalStatesOfM1)
            {
                S.IsAcceptable = false; // no longer acceptable
                S.NextStates.AddRange(m2.States);
            }

            return m;
        }

        private void MarkFinalStatesAs(ref GIMachine m, string sFinalStateName)
        {
            List<GIMachineWordState> finalStates = GetFinalStates(m);
            if (finalStates != null)
            {
                foreach (GIMachineWordState S in finalStates)
                {
                    S.Name = sFinalStateName;
                }
            }
        }

        private List<GIMachineWordState> GetFinalStates(GIMachine m1)
        {
            List<GIMachineWordState> result = new List<GIMachineWordState>();

            foreach (GIMachineWordState S in m1.States)
            {
                result.AddRange(GetFinalSatesFromState(S));
            }

            return result;
        }

        private GIMachine GetMachineFromParsedTable(string sName)
        {
            if (_publicMachineTable.ContainsKey(sName))
            {
                return _publicMachineTable[sName];
            }
            else if(_privateMachineTable.ContainsKey(sName))
            {
                return _privateMachineTable[sName];
            }

            throw new Exception(string.Format("Error: Missing pattern named '{0}', may be it is being referred before its definition.", sName));
        }


        private List<GIMachineWordState> GetFinalSatesFromState(GIMachineWordState S)
        {
            List<GIMachineWordState> result = new List<GIMachineWordState>();
            if (S != null && (S.NextStates == null || S.NextStates.Count == 0))
            {
                result.Add(S);
            }
            else
                if (S != null && S.NextStates != null && S.NextStates.Count > 0)
                {
                    foreach (GIMachineWordState S2 in S.NextStates)
                    {
                        result.AddRange( GetFinalSatesFromState( S2 )  );
                    }
                }


            return result;
        }

        private void AddPublicMachine(GIMachine omachine)
        {
            if (_publicMachineTable.ContainsKey(omachine.Name)) throw new Exception(string.Format("Error: Duplicate pattern named '{0}'", omachine.Name));
            _publicMachineTable.Add(omachine.Name, omachine);
        }

        private void AddPrivateMachine(GIMachine omachine)
        {
            if (_privateMachineTable.ContainsKey(omachine.Name)) throw new Exception(string.Format("Error: Duplicate pattern named '{0}'", omachine.Name));
            _privateMachineTable.Add(omachine.Name, omachine);
        }

        private void TokenErrorEvent(LALRParser parser, TokenErrorEventArgs args)
        {
            //MainForm.mainForm.ShowInputError(args.Token);
            //MainForm.mainForm.WriteResult("Token error with input: '" + args.Token.ToString() + "'");
        }

        private void ParseErrorEvent(LALRParser parser, ParseErrorEventArgs args)
        {
            //MainForm.mainForm.ShowInputError(args.UnexpectedToken);
            //MainForm.mainForm.WriteResult("Parse error caused by token: '" + args.UnexpectedToken.ToString() + "'");
        }
    }


    [Serializable()]
    public class SymbolException : System.Exception
    {
        public SymbolException(string message)
            : base(message)
        {
        }

        public SymbolException(string message,
            Exception inner)
            : base(message, inner)
        {
        }

        protected SymbolException(SerializationInfo info,
            StreamingContext context)
            : base(info, context)
        {
        }

    }

    [Serializable()]
    public class RuleException : System.Exception
    {

        public RuleException(string message)
            : base(message)
        {
        }

        public RuleException(string message,
            Exception inner)
            : base(message, inner)
        {
        }

        protected RuleException(SerializationInfo info,
            StreamingContext context)
            : base(info, context)
        {
        }

    }

    public class ParseFlowContext
    {
        public bool IsPrivate { get; set; }
        public GIMachine MachineSoFar { get; set; }

        public ParseFlowContext(bool isPrivate, GIMachine oMachine)
        {
            this.IsPrivate = isPrivate;
            this.MachineSoFar = oMachine;
        }
    }
}
